package com.ymx.darling.wrapper;

import com.ymx.darling.bean.entity.BeanInfo;
import com.ymx.darling.bean.entity.BeanInfoManager;
import com.ymx.darling.bean.entity.FieldInfo;
import com.ymx.darling.util.LambdaUtil;
import com.ymx.darling.util.StringUtil;
import com.ymx.darling.wrapper.fragment.*;
import com.ymx.darling.wrapper.fragment.lambda.LambdaGetter;
import com.ymx.darling.wrapper.fragment.lambda.WhereLambda;
import com.ymx.darling.wrapper.fragment.lambda.WrapperLambda;
import com.ymx.darling.wrapper.build.SelectBuilder;
import com.ymx.darling.bean.sql.SqlStatement;

import java.util.*;

/**
 * 动态构建查询sql
 *
 * @author 爱java的小于
 */
public class QueryWrapper extends Wrapper<QueryWrapper> {
    /**这两个字段不同于分页数据 只是单纯的指定从第offset开始截取limit行数的数据*/
    private Long offset;
    private Long limit;
    /**一下字段保存分段sql*/
    private final SelectSegment select = new SelectSegment(this);
    private final FromSegment from = new FromSegment();
    private final JoinSegment joins = new JoinSegment();
    private final GroupBySegment groupBy = new GroupBySegment();
    private final HavingSegment having = new HavingSegment();
    private final UnionSegment union = new UnionSegment();
    private final OrderBySegment orderBy = new OrderBySegment();
    /**内嵌select from where中QueryWrapper的集合*/
    private final List<QueryWrapper> nestedQueryWrappers = new ArrayList<>();

    /**
     * 返回QueryWrapper占位符的名称
     * @param queryWrapper qw
     * @param asName 生成占位符时是否存在别名
     * @return String
     */
    private String getQueryWrapperPlaceholder(QueryWrapper queryWrapper, String asName) {
        Objects.requireNonNull(queryWrapper, "不能将null代替QueryWrapper作为sql片段");
        String queryWrapperPlaceholder;
        int placeholderIndex = this.nestedQueryWrappers.size();
        if (StringUtil.isNull(asName)) {
            queryWrapperPlaceholder = String.format("{%d}", placeholderIndex);
        } else {
            queryWrapperPlaceholder = String.format("{%d} AS %s", placeholderIndex, asName);
        }
        this.nestedQueryWrappers.add(queryWrapper);
        return queryWrapperPlaceholder;
    }

    protected List<QueryWrapper> getNestedQueryWrappers() {
        return nestedQueryWrappers;
    }

    public SqlStatement sqlSelect() {
        return this.select.getSqlStatement();
    }

    protected Long getOffset() {
        return this.offset;
    }

    protected Long getLimit() {
        return this.limit;
    }

    public QueryWrapper select(String... colum) {
        this.select.select(colum);
        return this;
    }

    public QueryWrapper select(QueryWrapper... queryWrappers) {
        for (QueryWrapper queryWrapper : queryWrappers) {
            this.selectAs(queryWrapper, "");
        }
        return this;
    }

    @SafeVarargs
    public final <L> QueryWrapper select(LambdaGetter<L>... columns) {
        List<String> columnList = new ArrayList<>();
        for (LambdaGetter<L> lambdaGetter : columns) {
            columnList.add(LambdaUtil.getColumnName(lambdaGetter));
        }
        return this.select(columnList.toArray(new String[0]));
    }

    public QueryWrapper select(Class<?>... types) {
        List<String> columnList = new ArrayList<>();
        for (Class<?> type : types) {
            BeanInfo beanInfo = BeanInfoManager.getBeanInfo(type);
            for (FieldInfo fieldInfo : beanInfo.getQueryColumn()) {
                columnList.add(String.format("%s.%s", beanInfo.getTableName(), fieldInfo.getColumn()));
            }
        }
        return this.select(columnList.toArray(new String[0]));
    }

    public QueryWrapper select(String[]... columns) {
        for (String[] cols : columns) {
            this.select(cols);
        }
        return this;
    }

    public QueryWrapper select(String[] columns, String... cols) {
        this.select(columns);
        this.select(cols);
        return this;
    }

    public QueryWrapper selectAs(String column, String asName) {
        return this.select(String.format("%s AS %s", column, asName));
    }

    public <L> QueryWrapper selectAs(LambdaGetter<L> column, String asName) {
        return this.selectAs(LambdaUtil.getColumnName(column), asName);
    }

    public QueryWrapper selectAs(QueryWrapper columnWrapper, String asName) {
        this.select(this.getQueryWrapperPlaceholder(columnWrapper, asName));
        return this;
    }

    public QueryWrapper selectDistinct() {
        select.distinct();
        return this;
    }

    public QueryWrapper selectSegment(String template, Object... params) {
        select.selectSegment(template, params);
        return this;
    }

    public QueryWrapper from(String... tables) {
        this.from.from(tables);
        return this;
    }

    public QueryWrapper from(Class<?>... fromTypes) {
        List<String> fromList = new ArrayList<>();
        for (Class<?> entityClass : fromTypes) {
            BeanInfo beanInfo = BeanInfoManager.getBeanInfo(entityClass);
            fromList.add(beanInfo.getTableName());
        }
        return this.from(fromList.toArray(new String[0]));
    }

    public QueryWrapper from(TableInfo... tableInfos) {
        List<String> fromList = new LinkedList<>();
        for (TableInfo tableInfo : tableInfos) {
            fromList.add(tableInfo.getTableName());
        }
        this.from(fromList.toArray(new String[0]));

        return this;
    }

    public QueryWrapper from(QueryWrapper subQuery, String asName) {
        this.from(this.getQueryWrapperPlaceholder(subQuery, asName));
        return this;
    }


    public JoinSegment.Joiner join(String tableName){
        return joins.join(tableName, JoinSegment.JOIN, this);
    }

    public JoinSegment.Joiner join(Class<?> joinType) {
        BeanInfo joinBeanInfo = BeanInfoManager.getBeanInfo(joinType);
        return this.join(joinBeanInfo.getTableName());
    }

    public JoinSegment.Joiner join(TableInfo tableInfo) {
        return this.join(tableInfo.getTableName());
    }

    public JoinSegment.Joiner join(QueryWrapper joinSubWrapper, String asName) {
        return this.join(this.getQueryWrapperPlaceholder(joinSubWrapper, asName));
    }

    public JoinSegment.Joiner join(QueryWrapper joinSubWrapper, TableInfo tableInfo) {
        return this.join(joinSubWrapper, tableInfo.getAliaName());
    }

    public JoinSegment.Joiner leftJoin(String tableName){
        return joins.join(tableName, JoinSegment.LEFT_JOIN, this);
    }

    public JoinSegment.Joiner leftJoin(Class<?> joinType) {
        BeanInfo joinBeanInfo = BeanInfoManager.getBeanInfo(joinType);
        return this.leftJoin(joinBeanInfo.getTableName());
    }

    public JoinSegment.Joiner leftJoin(TableInfo tableInfo) {
        return this.leftJoin(tableInfo.getTableName());
    }

    public JoinSegment.Joiner leftJoin(QueryWrapper joinWrapper, String asName) {
        return this.leftJoin(this.getQueryWrapperPlaceholder(joinWrapper, asName));
    }

    public JoinSegment.Joiner leftJoin(QueryWrapper joinSubWrapper, TableInfo tableInfo) {
        return this.leftJoin(joinSubWrapper, tableInfo.getAliaName());
    }

    public JoinSegment.Joiner rightJoin(String tableName){
        return joins.join(tableName, JoinSegment.RIGHT_JOIN, this);
    }

    public JoinSegment.Joiner rightJoin(Class<?> joinType) {
        BeanInfo joinBeanInfo = BeanInfoManager.getBeanInfo(joinType);
        return this.rightJoin(joinBeanInfo.getTableName());
    }

    public JoinSegment.Joiner rightJoin(TableInfo tableName) {
        return rightJoin(tableName.getTableName());
    }

    public JoinSegment.Joiner rightJoin(QueryWrapper joinWrapper, String asName) {
        return this.rightJoin(this.getQueryWrapperPlaceholder(joinWrapper, asName));
    }

    public JoinSegment.Joiner rightJoin(QueryWrapper joinSubWrapper, TableInfo tableInfo) {
        return this.rightJoin(joinSubWrapper, tableInfo.getAliaName());
    }

    public QueryWrapper inSql(String column, QueryWrapper wrapper) {
        return this.inSql(column, this.getQueryWrapperPlaceholder(wrapper, null));
    }

    public <L> QueryWrapper inSql(LambdaGetter<L> column, QueryWrapper wrapper) {
        return this.inSql(LambdaUtil.getColumnName(column), wrapper);
    }

    public QueryWrapper eqInSql(String column, QueryWrapper wrapper) {
        return this.eqInSql(column, this.getQueryWrapperPlaceholder(wrapper, null));
    }

    public <L> QueryWrapper eqInSql(LambdaGetter<L> column, QueryWrapper wrapper) {
        return this.eqInSql(LambdaUtil.getColumnName(column), wrapper);
    }

    public QueryWrapper neInSql(String column, QueryWrapper wrapper) {
        return this.neInSql(column, this.getQueryWrapperPlaceholder(wrapper, null));
    }

    public <L> QueryWrapper neInSql(LambdaGetter<L> column, QueryWrapper wrapper) {
        return this.neInSql(LambdaUtil.getColumnName(column), wrapper);
    }

    public QueryWrapper gtInSql(String column, QueryWrapper wrapper) {
        return this.gtInSql(column, this.getQueryWrapperPlaceholder(wrapper, null));
    }

    public <L> QueryWrapper gtInSql(LambdaGetter<L> column, QueryWrapper wrapper) {
        return this.gtInSql(LambdaUtil.getColumnName(column), wrapper);
    }

    public QueryWrapper geInSql(String column, QueryWrapper wrapper) {
        return this.geInSql(column, this.getQueryWrapperPlaceholder(wrapper, null));
    }

    public <L> QueryWrapper geInSql(LambdaGetter<L> column, QueryWrapper wrapper) {
        return this.geInSql(LambdaUtil.getColumnName(column), wrapper);
    }

    public QueryWrapper ltInSql(String column, QueryWrapper wrapper) {
        return this.ltInSql(column, this.getQueryWrapperPlaceholder(wrapper, null));
    }

    public <L> QueryWrapper ltInSql(LambdaGetter<L> column, QueryWrapper wrapper) {
        return this.ltInSql(LambdaUtil.getColumnName(column), wrapper);
    }

    public QueryWrapper leInSql(String column, QueryWrapper wrapper) {
        return this.leInSql(column, this.getQueryWrapperPlaceholder(wrapper, null));
    }

    public <L> QueryWrapper leInSql(LambdaGetter<L> column, QueryWrapper wrapper) {
        return this.leInSql(LambdaUtil.getColumnName(column), wrapper);
    }

    public QueryWrapper exists(WrapperLambda wrapperLambda) {
        QueryWrapper queryWrapper = wrapperLambda.subWrapper(new QueryWrapper());
        return this.exists(this.getQueryWrapperPlaceholder(queryWrapper, null));
    }

    public QueryWrapper notExists(WrapperLambda wrapperLambda) {
        QueryWrapper queryWrapper = wrapperLambda.subWrapper(new QueryWrapper());
        return this.notExists(this.getQueryWrapperPlaceholder(queryWrapper, null));
    }

    public QueryWrapper groupBy(String... colum){
        this.groupBy.splitGroupBy(colum);
        return this;
    }

    @SafeVarargs
    public final <L> QueryWrapper groupBy(LambdaGetter<L>... columns) {
        List<String> groupByColumnList = new ArrayList<>();
        for (LambdaGetter<L> columnLambda : columns) {
            groupByColumnList.add(LambdaUtil.getColumnName(columnLambda));
        }
        return this.groupBy(groupByColumnList.toArray(new String[0]));
    }

    public QueryWrapper orderByAsc(String colum){
        this.orderBy.orderByAsc(colum);
        return this;
    }

    public <L> QueryWrapper orderByAsc(LambdaGetter<L> column) {
        return this.orderByAsc(LambdaUtil.getColumnName(column));
    }

    public QueryWrapper orderByDes(String colum){
        this.orderBy.orderByDes(colum);
        return this;
    }

    public <L> QueryWrapper orderByDes(LambdaGetter<L> column) {
        return this.orderByDes(LambdaUtil.getColumnName(column));
    }

    public QueryWrapper union(QueryWrapper wrapper){
        this.union.union(this.getQueryWrapperPlaceholder(wrapper, null));
        return this;
    }

    public QueryWrapper unionAll(QueryWrapper wrapper){
        this.union.unionAll(this.getQueryWrapperPlaceholder(wrapper, null));
        return this;
    }

    public QueryWrapper union(WrapperLambda consumer){
        return this.union(consumer.subWrapper(new QueryWrapper()));
    }

    public QueryWrapper unionAll(WrapperLambda consumer){
        return this.unionAll(consumer.subWrapper(new QueryWrapper()));
    }

    public QueryWrapper having(WhereLambda consumer){
        this.having.splitHaving(consumer);
        return this;
    }

    public QueryWrapper having(String havingSql, Object... param) {
        this.having.splitHaving(havingSql, param);
        return this;
    }

    public QueryWrapper limit(Number limit) {
        this.limit = limit.longValue();
        return this;
    }

    public QueryWrapper limit(Number offset, Number limit) {
        this.offset = offset.longValue();
        this.limit = limit.longValue();
        return this;
    }

    public SqlStatement toSQL() {
        return this.toSql(null);
    }

    public SqlStatement toSql(Class<?> entityType) {
        BeanInfo beanInfo = entityType != null ? BeanInfoManager.getBeanInfo(entityType) : null;
        return new SelectBuilder().buildSelect(this, beanInfo);
    }

    protected List<String> getQueryFroms() {
        return this.from.getTables();
    }

    protected List<String> getJoinFroms() {
        return this.joins.getTables();
    }

    @Override
    protected Map<String, SqlSegment> getSqlSegmentMap() {
        Map<String, SqlSegment> sqlSegmentMap = super.getSqlSegmentMap();
        sqlSegmentMap.put("select", this.select);
        sqlSegmentMap.put("from", this.from);
        sqlSegmentMap.put("join", this.joins);
        sqlSegmentMap.put("having", this.having);
        sqlSegmentMap.put("union", this.union);
        sqlSegmentMap.put("groupBy", this.groupBy);
        sqlSegmentMap.put("orderBy", this.orderBy);

        return sqlSegmentMap;
    }
}
